package com.nttdata.ta.user.config;

import com.nttdata.ta.common.dto.UserDTO;
import com.nttdata.ta.common.util.JwtUtil;
import com.nttdata.ta.user.service.UserService;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    private final UserService userService;
    private final JwtUtil jwtUtil;
    
    // 用户信息缓存，key是token，value是用户信息
    private final Map<String, CachedUserInfo> userCache = new ConcurrentHashMap<>();
    
    public JwtAuthenticationFilter(@Lazy UserService userService, JwtUtil jwtUtil) {
        this.userService = userService;
        this.jwtUtil = jwtUtil;
        
        // 每10分钟清理一次过期的缓存
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(this::cleanExpiredCache, 10, 10, TimeUnit.MINUTES);
        log.info("JWT认证过滤器已初始化，启用缓存机制");
    }
    
    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request, 
            @NonNull HttpServletResponse response, 
            @NonNull FilterChain filterChain)
            throws ServletException, IOException {
        
        // 不拦截认证相关API和OPTIONS请求
        String path = request.getRequestURI();
        String method = request.getMethod();
        
        if (path.startsWith("/api/auth") || "OPTIONS".equals(method)) {
            filterChain.doFilter(request, response);
            return;
        }
        
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            
            try {
                // 先从缓存中获取用户信息
                CachedUserInfo cachedInfo = userCache.get(token);
                
                if (cachedInfo != null && !cachedInfo.isExpired()) {
                    // 缓存命中，使用缓存的用户信息设置认证
                    log.debug("使用缓存的用户信息进行认证，用户: {}", cachedInfo.getUsername());
                    setAuthentication(cachedInfo.getUsername(), cachedInfo.getRole());
                } else {
                    // 缓存未命中或已过期，查询数据库
                    authenticateFromDatabase(token);
                }
            } catch (Exception e) {
                log.error("JWT认证失败", e);
            }
        }
        
        filterChain.doFilter(request, response);
    }
    
    private void authenticateFromDatabase(String token) {
        try {
            String username = jwtUtil.getUsernameFromToken(token);
            boolean isValid = userService.validateToken(token);
            
            if (isValid && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 获取用户ID
                Long userId = jwtUtil.getUserIdFromToken(token);
                log.info("从数据库获取用户信息，用户ID: {}", userId);
                
                // 从数据库获取用户信息和角色
                UserDTO userDTO = userService.getUserById(userId);
                
                // 检查用户是否被禁用
                if (!userDTO.isEnabled()) {
                    log.warn("用户 {} 已被禁用，拒绝JWT认证", username);
                    return;
                }
                
                String role = userDTO.getRole();
                
                // 创建认证对象并设置
                setAuthentication(username, role);
                
                // 保存到缓存（有效期为token的有效期或最长1小时）
                Date expiration = jwtUtil.getExpirationDateFromToken(token);
                long expirationTime = Math.min(
                    expiration.getTime(), 
                    System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)
                );
                
                userCache.put(token, new CachedUserInfo(
                    username, 
                    role, 
                    new Date(expirationTime)
                ));
                
                log.info("用户 {} 信息已缓存，角色: {}, 过期时间: {}", username, role, new Date(expirationTime));
            }
        } catch (Exception e) {
            log.error("从数据库获取用户信息失败", e);
        }
    }
    
    private void setAuthentication(String username, String role) {
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                username, null, Collections.singletonList(new SimpleGrantedAuthority(role))
        );
        
        SecurityContextHolder.getContext().setAuthentication(authToken);
        log.debug("用户 {} 已通过JWT认证，角色: {}", username, role);
    }
    
    private void cleanExpiredCache() {
        log.debug("开始清理过期的用户缓存...");
        int beforeSize = userCache.size();
        
        userCache.entrySet().removeIf(entry -> entry.getValue().isExpired());
        
        int afterSize = userCache.size();
        log.debug("缓存清理完成，删除了 {} 条过期记录，当前缓存大小: {}", beforeSize - afterSize, afterSize);
    }
    
    /**
     * 缓存的用户信息类
     */
    @Getter
    private static class CachedUserInfo {
        private final String username;
        private final String role;
        private final Date expirationTime;
        
        public CachedUserInfo(String username, String role, Date expirationTime) {
            this.username = username;
            this.role = role;
            this.expirationTime = expirationTime;
        }
        
        public boolean isExpired() {
            return expirationTime.before(new Date());
        }
    }
} 